/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package semant.second_pass;

import com.sun.org.apache.xalan.internal.xsltc.util.IntegerArray;
import semant.Env;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.Symbol;
import symbol.VarInfo;
import syntaxtree.And;
import syntaxtree.ArrayLength;
import syntaxtree.ArrayLookup;
import syntaxtree.BooleanType;
import syntaxtree.Call;
import syntaxtree.Equal;
import syntaxtree.Exp;
import syntaxtree.False;
import syntaxtree.IdentifierExp;
import syntaxtree.IdentifierType;
import syntaxtree.IntArrayType;
import syntaxtree.IntegerLiteral;
import syntaxtree.IntegerType;
import syntaxtree.LessThan;
import syntaxtree.Minus;
import syntaxtree.NewArray;
import syntaxtree.NewObject;
import syntaxtree.Not;
import syntaxtree.Plus;
import syntaxtree.This;
import syntaxtree.Times;
import syntaxtree.True;
import syntaxtree.Type;
import syntaxtree.TypeVisitorAdapter;

/**
 *
 * @author Thiago
 */
class ExpHandler extends TypeVisitorAdapter {

    Env env;
    ClassInfo info;
    MethodInfo method;

    private ExpHandler(Env e, ClassInfo c, MethodInfo m) {
        env = e;
        info = c;
        method = m;

    }

    static Type secondPass(Env e, ClassInfo c, MethodInfo m, Exp node) {
        ExpHandler h = new ExpHandler(e, c, m);
        return node.accept(h);
    }

    @Override
    public Type visit(LessThan node) {
        Type lhs = ExpHandler.secondPass(env, info, method, node.lhs);

        Type rhs = ExpHandler.secondPass(env, info, method, node.rhs);

        if (!(lhs instanceof IntegerType)) {
            env.err.Error(node.lhs, new Object[]{
                        "Tipo invalido para lado esquerdo do operador \'<\'",
                        "Esperado: Integer",
                        "Encontrado: " + lhs});
        }

        if (!(rhs instanceof IntegerType)) {
            env.err.Error(node.rhs, new Object[]{
                        "Tipo invalido para lado direito do operador \'<\'",
                        "Esperado: Integer",
                        "Encontrado: " + rhs});
        }

        return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(IdentifierExp node) {
        Symbol name = Symbol.symbol(node.name.s);

        VarInfo v = ExpHandler.getVariable(info, method, name);

        if (v == null) {
            env.err.Error(node, new Object[]{
                        "Identificador \'" + name + "\' nao definido no metodo atual."});

            return new IntegerType(node.line, node.row);
        }

        return node.type = v.type;
    }

    @Override
    public Type visit(And node) {
        Type lhs = ExpHandler.secondPass(env, info, method, node.lhs);

        Type rhs = ExpHandler.secondPass(env, info, method, node.rhs);

        if (!(lhs instanceof BooleanType)) {
            env.err.Error(node.lhs, new Object[]{
                        "Tipo invalido para lado esquerdo do operador \'&&\'",
                        "Esperado: Boolean",
                        "Encontrado: " + lhs});
        }

        if (!(rhs instanceof BooleanType)) {
            env.err.Error(node.rhs, new Object[]{
                        "Tipo invalido para lado direito do operador \'&&\'",
                        "Esperado: Boolean",
                        "Encontrado: " + rhs});
        }

        return node.type = new BooleanType(node.line, node.row);
    }

    @Override
    public Type visit(ArrayLookup node) {

        Type array = ExpHandler.secondPass(env,info,method,node.array);
        Type pos = ExpHandler.secondPass(env,info,method,node.index);

        if (!(array instanceof IntArrayType)) {
            env.err.Error(node.array, new Object[]{
                        "O tipo dessa variÃ¡vel nÃ£o Ã© um vetor",
                        "Esperado: IntArray",
                        "Encontrado: " + array});
        }

        if (!(pos instanceof IntegerType)) {
            env.err.Error(node.index, new Object[]{
                        "Tipo invalido para indice do vetor",
                        "Esperado: Integer",
                        "Encontrado: " + pos});
        }

        return node.type = new IntegerType(node.line,node.row);
    }

    public Type visit(ArrayLength node) {

        Type array = ExpHandler.secondPass(env,info,method,node.array);

        if (!(array instanceof IntArrayType)) {
            env.err.Error(node.array, new Object[]{
                        "O tipo dessa variÃ¡vel nÃ£o Ã© um vetor",
                        "Esperado: IntArray",
                        "Encontrado: " + array});
        }

        return node.type = new IntegerType(node.line,node.row);
    }

    public Type visit(Call node) {

        Type obj = ExpHandler.secondPass(env, info, method, node.object);
        if (!(obj instanceof IdentifierType)) {
            env.err.Error(node.object, new Object[]{
                        "Esse objeto nao e uma instancia de classe que contem metodos",
                        "Esperado: IdentifierType",
                        "Encontrado: " + obj});
            return node.type = new IntegerType(node.line,node.row);
        }
        ClassInfo class_obj = env.classes.get(Symbol.symbol(((IdentifierType)obj).name));
        MethodInfo metodo = class_obj.methods.get(Symbol.symbol(node.method.s));
        if(metodo == null)
        {
            env.err.Error(node.object, new Object[]{
                    "Esse metodo nao existe na classe: "+ class_obj.name.toString()});
            return node.type = new IntegerType(node.line,node.row);
        }
        FormalHandler.secondPass(env,class_obj,info,method,node);
        return node.type = metodo.type;

    }

    public Type visit(Equal node) {
        boolean isvalid = true;
        Type lhs = ExpHandler.secondPass(env, info, method, node.lhs);

        Type rhs = ExpHandler.secondPass(env, info, method, node.rhs);

        if (!(lhs instanceof IntegerType) && !(lhs instanceof BooleanType)) {
            env.err.Error(node.lhs, new Object[]{
                        "Tipo invalido para lado esquerdo do operador \'=\'",
                        "Esperado: Integer ou Boolean",
                        "Encontrado: " + lhs});
            isvalid = false;

        }
        if(!(rhs instanceof IntegerType) && !(rhs instanceof BooleanType))
        {
            env.err.Error(node.rhs, new Object[]{
                        "Tipo invalido para lado direito do operador \'=\'",
                        "Esperado: Integer ou Boolean",
                        "Encontrado: " + rhs});
            isvalid = false;
        }
        if(isvalid){
           if(lhs != rhs){
            env.err.Error(node.rhs, new Object[]{
                        "Tipo invalido para lado direito do operador \'=\'",
                        "Esperado: " + lhs,
                        "Encontrado: " + rhs});
           }
        }

        return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(False node) {
        BooleanType b = new BooleanType(node.line,node.row);
        return node.type = b;
    }

    public Type visit(IntegerLiteral node) {
        return node.type = new IntegerType(node.line,node.row);
    }

    public Type visit(Minus node) {
        Type lhs = ExpHandler.secondPass(env, info, method, node.lhs);

        Type rhs = ExpHandler.secondPass(env, info, method, node.rhs);

        if (!(lhs instanceof IntegerType)) {
            env.err.Error(node.lhs, new Object[]{
                        "Tipo invalido para lado esquerdo do operador \'-\'",
                        "Esperado: Integer",
                        "Encontrado: " + lhs});
        }

        if (!(rhs instanceof IntegerType)) {
            env.err.Error(node.rhs, new Object[]{
                        "Tipo invalido para lado direito do operador \'-\'",
                        "Esperado: Integer",
                        "Encontrado: " + rhs});
        }

        return node.type = new IntegerType(node.line,node.row);
    }

    public Type visit(NewArray node) {
        Type size = ExpHandler.secondPass(env, info, method, node.size);
        if(!(size instanceof IntegerType))
        {
            env.err.Error(node,new Object[]{"Tipo invalido para tamanho do vetor.",
                                            "Esperado: Integer",
                                            "Encontrado: "+ size});
        }
        return node.type = new IntArrayType(node.line,node.row);
    }

    public Type visit(NewObject node) {
       return node.type = new IdentifierType(node.line,node.row,node.className.s);
    }

    public Type visit(Not node) {

        Type exp = ExpHandler.secondPass(env, info, method, node.exp);

        if (!(exp instanceof BooleanType)) {
            env.err.Error(exp, new Object[]{
                        "NÃ£o Ã© possivel negar esse tipo de expressÃ£o",
                        "Esperado: Boolean",
                        "Encontrado: " + exp});
        }
        return node.type = new BooleanType(node.line, node.row);
    }

    public Type visit(Plus node) {
        Type lhs = ExpHandler.secondPass(env, info, method, node.lhs);

        Type rhs = ExpHandler.secondPass(env, info, method, node.rhs);

        if (!(lhs instanceof IntegerType)) {
            env.err.Error(node.lhs, new Object[]{
                        "Tipo invalido para lado esquerdo do operador \'+\'",
                        "Esperado: Integer",
                        "Encontrado: " + lhs});
        }

        if (!(rhs instanceof IntegerType)) {
            env.err.Error(node.rhs, new Object[]{
                        "Tipo invalido para lado direito do operador \'+\'",
                        "Esperado: Integer",
                        "Encontrado: " + rhs});
        }

        return node.type = new IntegerType(node.line,node.row);
    }

    public Type visit(This node) {
        Symbol s = Symbol.symbol("main");
        if(info.methods.keys().nextElement().equals(s) && info.base == null){
            env.err.Error(node, new Object[]{
                        "chamada de \'this\' vÃ¡lida apenas em mÃ©todos nÃ£o estÃ¡ticos"});
        }
        return new IdentifierType(node.line,node.row,info.name.toString());
    }

    public Type visit(Times node) {
        Type lhs = ExpHandler.secondPass(env, info, method, node.lhs);

        Type rhs = ExpHandler.secondPass(env, info, method, node.rhs);

        if (!(lhs instanceof IntegerType)) {
            env.err.Error(node.lhs, new Object[]{
                        "Tipo invalido para lado esquerdo do operador \'*\'",
                        "Esperado: Integer",
                        "Encontrado: " + lhs});
        }

        if (!(rhs instanceof IntegerType)) {
            env.err.Error(node.rhs, new Object[]{
                        "Tipo invalido para lado direito do operador \'*\'",
                        "Esperado: Integer",
                        "Encontrado: " + rhs});
        }

        return node.type = new IntegerType(node.line,node.row);
    }

    public Type visit(True node) {
        BooleanType b = new BooleanType(node.line,node.row);
        return node.type = b;
    }

    public static VarInfo getVariable(ClassInfo c, MethodInfo m, Symbol name) {

        VarInfo var = null;

        if (!(m == null)) {
            if (!m.localsTable.containsKey(name)) {
                if (!m.formalsTable.containsKey(name)) {
                    if (!(c == null)) {
                        if (c.attributes.containsKey(name)) {
                            var = c.attributes.get(name);
                        }
                    }
                } else {
                    var = m.formalsTable.get(name);
                }
            } else {
                var = m.localsTable.get(name);
            }
        }

        return var;
    }
}
